﻿using Mapster;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using YWM.Dto;
using YWM.Entities;
using YWM.Entities.Extensions;
using YWM.Repositories;

namespace YWM.Application
{
    public abstract class AsyncCrudService<TEntity, TEntityDto>
        : AsyncCrudService<TEntity, TEntityDto, long>
        where TEntity : class, IEntityOfTKey<long>
        where TEntityDto : IEntityDto<long>
    {
    }

    public abstract class AsyncCrudService<TEntity, TEntityDto, TKey>
        : AsyncCrudService<TEntity, TEntityDto, TKey, PagedAndSortedResultRequestDto>
        where TEntity : class, IEntityOfTKey<TKey>
        where TEntityDto : IEntityDto<TKey>
    {
    }

    public abstract class AsyncCrudService<TEntity, TEntityDto, TKey, TGetAllInput>
        : AsyncCrudService<TEntity, TEntityDto, TKey, TGetAllInput, TEntityDto, TEntityDto>
        where TEntity : class, IEntityOfTKey<TKey>
        where TEntityDto : IEntityDto<TKey>
    {
    }

    public abstract class AsyncCrudService<TEntity, TEntityDto, TKey, TGetAllInput, TCreateInput>
        : AsyncCrudService<TEntity, TEntityDto, TKey, TGetAllInput, TCreateInput, TCreateInput>
        where TGetAllInput : IPagedAndSortedResultRequest
        where TEntity : class, IEntityOfTKey<TKey>
        where TEntityDto : IEntityDto<TKey>
       where TCreateInput : IEntityDto<TKey>
    {
    }

    public abstract class AsyncCrudService<TEntity, TEntityDto, TKey, TGetAllInput, TCreateInput, TUpdateInput>
        : AsyncCrudService<TEntity, TEntityDto, TKey, TGetAllInput, TCreateInput, TUpdateInput, EntityDto<TKey>>
        where TEntity : class, IEntityOfTKey<TKey>
        where TEntityDto : IEntityDto<TKey>
        where TUpdateInput : IEntityDto<TKey>
    {
    }

    public abstract class AsyncCrudService<TEntity, TEntityDto, TKey, TGetAllInput, TCreateInput, TUpdateInput, TGetInput>
    : AsyncCrudService<TEntity, TEntityDto, TKey, TGetAllInput, TCreateInput, TUpdateInput, TGetInput, EntityDto<TKey>>
        where TEntity : class, IEntityOfTKey<TKey>
        where TEntityDto : IEntityDto<TKey>
        where TUpdateInput : IEntityDto<TKey>
        where TGetInput : IEntityDto<TKey>
    {
    }

    public abstract class AsyncCrudService<TEntity, TEntityDto, TKey, TGetAllInput, TCreateInput, TUpdateInput, TGetInput, TDeleteInput>
       : CrudServiceBase<TEntity, TEntityDto, TKey, TGetAllInput, TCreateInput, TUpdateInput>,
        IAsyncCrudService<TEntityDto, TKey, TGetAllInput, TCreateInput, TUpdateInput, TGetInput, TDeleteInput>
           where TEntity : class, IEntityOfTKey<TKey>
           where TEntityDto : IEntityDto<TKey>
           where TUpdateInput : IEntityDto<TKey>
           where TGetInput : IEntityDto<TKey>
           where TDeleteInput : IEntityDto<TKey>
    {
        protected IAsyncQueryableExecuter AsyncQueryableExecuter { get; set; }

        protected AsyncCrudService()
        {
            AsyncQueryableExecuter = YApp.Resolve<IAsyncQueryableExecuter>() ;
        }

        public virtual async Task<TEntityDto> Get(TGetInput input)
        {
            TEntity entity = await GetEntityByIdAsync(input.Id);
            return MapToEntityDto(entity);
        }

        public virtual async Task<PagedResultDto<TEntityDto>> GetAll(TGetAllInput input)
        {
            IQueryable<TEntity> query = CreateFilteredQuery(input);

            int totalCount = await AsyncQueryableExecuter.CountAsync(query);

            query = ApplySorting(query, input);
            query = ApplyPaging(query, input);

            List<TEntity> entities = await AsyncQueryableExecuter.ToListAsync(query);

            PagedResultDto<TEntityDto> pagedResultDto = new PagedResultDto<TEntityDto>(
                totalCount,
                entities.Select(MapToEntityDto).ToList()
            );
            return await Task.FromResult(pagedResultDto);
        }

        public virtual async Task<TEntityDto> Create(TCreateInput input)
        {
            TEntity entity = MapToEntity(input);

            await Repository.InsertAsync(entity);

            await DbContextPool.SavePoolNowAsync();

            return MapToEntityDto(entity);
        }

        public virtual async Task<TEntityDto> Update(TUpdateInput input)
        {
            return await UpdateAsync(input);
        }

        public virtual async Task Delete(TDeleteInput input)
        {
            if (input.IsSoftDelete())
            {
                await UpdateAsync(input);
            }
            else
            {
                await Repository.DeleteAsync(input.Id);

                await DbContextPool.SavePoolNowAsync();
            }
        }

        private async Task<TEntityDto> UpdateAsync<TInput>(TInput input) where TInput : IEntityDto<TKey>
        {
            TEntity entity = await GetEntityByIdAsync(input.Id);

            entity = input.Adapt(entity);

            await DbContextPool.SavePoolNowAsync();

            return MapToEntityDto(entity);
        }

        protected virtual Task<TEntity> GetEntityByIdAsync(TKey id)
        {
            return Repository.GetAsync(id);
        }
    }
}
